
#ifndef ELEMENT_H_
#define ELEMENT_H_

#include "Core.h"
#include "Aabb2.h"
#include "Material.h"

#define INFINITE_MASS           100000.f

namespace p2 {

class Entity;
class Element: public RealTimeType
{
DECLARE_RTT(Element, RealTimeType);

	/*
	 * REF: http://www.cplusplus.com/reference/set/set/
	 */
	struct planeCompare {
		bool operator()(Element const* x, Element const* y) const {
			return x->_plane == y->_plane ? x < y : x->_plane < y->_plane;
		}
	};
public:
	typedef std::set<Element* , planeCompare, boost::fast_pool_allocator<Element* > > Set;

public:
	virtual ~Element();

	virtual bool updateShapeData() = 0;
	virtual void updateMotionBounds(real64 dT) = 0;
	virtual void updateWorldSpace() = 0;
	virtual bool contains(Vector2<real> worldPoint) = 0;

	Matrix4<real> getModelViewMatrix();
	virtual Matrix4<real> getFutureWorldMatrix(real64 dT);

	inline const Vector2<real>& getCenterOfMass() const;
	inline const Vector2<real>& getHalfExtent() const;
	inline void setHalfExtent(const Vector2<real>& halfExtent);
	inline real getInertia() const;
	inline void setInertia(real inertia);
	inline real getInvInertia() const;
	inline void setInvInertia(real invInertia);
	inline real getInvMass() const;
	inline void setInvMass(real invMass);
	inline const p2Material& getMaterial() const;
	inline void setMaterial(const p2Material& material);
	inline real getMass() const;
	inline void setMass(real mass);
	inline Aabb2<real>& getMotionBounds();
	inline Entity const* getOwner() const;
	inline Entity* getOwner();
	inline void setOwner(Entity* owner);
	inline const Vector2<real>& getPosition() const;
	inline void setPosition(Vector2<real> const position);
	inline real getRotation() const;
	inline void setRotation(real radians);
	inline int32 getSapObjectId() const;
	inline void setSapObjectId(int32 sapObjectId);
	inline void* getUserData() const;
	inline void setUserData(void* userData);
	inline Aabb2<real>& getWorldBounds();
	inline Matrix4<real> const& getWorldMatrix() const;
	inline Vector2<real> const& getWorldPosition() const
	{
		return _worldPosition;
	}
	inline Transform2<real> const& getTransform() const
	{
		return _transform;
	}
	inline uint32 getPlane() const
	{
		return _plane;
	}
	inline void setPlane(uint32 plane)
	{
		_plane = plane;
	}

protected:
	inline Element(real mass, Vector2<real> localPosition, real localAngle, p2Material const& material, uint32 plane, void* userData);
	virtual void updateTransform();

protected:
	Entity* _owner;

	real _mass;
	real _invMass;
	real _inertia;
	real _invInertia;

	p2Material _material;

	Vector2<real> _worldPosition;
	Vector2<real> _x;
	real _a;
	Transform2<real> _transform;
	Matrix4<real> _worldMatrix;

	real _volume;
	Vector2<real> _centerOfMass;
    Vector2<real> _halfExtent;
	Aabb2<real> _worldBounds;
	Aabb2<real> _motionBounds;

	int32 _sapObjectId;

	uint32 _plane;

	void *_userData;
};

inline const Vector2<real>& Element::getCenterOfMass() const
{
	return _centerOfMass;
}

inline const Vector2<real>& Element::getHalfExtent() const
{
	return _halfExtent;
}

inline void Element::setHalfExtent(const Vector2<real>& halfExtent)
{
	_halfExtent = halfExtent;
}

inline real Element::getInertia() const
{
	return _inertia;
}

inline void Element::setInertia(real inertia)
{
	ASSERTLOG(inertia > 0.f, "Element::setInertia");
    _inertia = inertia;
    _invInertia = (_inertia > 0.f) ? (1.f / _inertia) : 0.f;
}

inline real Element::getInvInertia() const
{
	return _invInertia;
}

inline void Element::setInvInertia(real invInertia)
{
	ASSERTLOG(invInertia > 0.f, "Element::setInvInertia");
    _invInertia = invInertia;
    _inertia = (_invInertia > 0.f) ? (1.f / _invInertia) : 0.f;
}

inline real Element::getInvMass() const
{
	return _invMass;
}

inline void Element::setInvMass(real invMass)
{
	ASSERTLOG(invMass > 0.f, "Element::setInvMass");
	_invMass = invMass;
    _mass = _invMass == 0.f ? INFINITE_MASS : (1.f / _invMass);
}

inline const p2Material& Element::getMaterial() const
{
	return _material;
}

inline void Element::setMaterial(const p2Material& material)
{
	_material = material;
}

inline real Element::getMass() const
{
	return _mass;
}

inline void Element::setMass(real mass)
{
	ASSERTLOG(mass > 0.f, "Element::setMass");
    _mass = Math<real>::Min(mass, INFINITE_MASS);
    _invMass = _mass == INFINITE_MASS ? 0.f : (1.f / _mass);
}

inline Aabb2<real>& Element::getMotionBounds()
{
	return _motionBounds;
}

Entity const* Element::getOwner() const
{
	return _owner;
}

Entity* Element::getOwner()
{
	return _owner;
}

void Element::setOwner(Entity* owner)
{
	_owner = owner;
}

const Vector2<real>& Element::getPosition() const
{
	return _x;
}

void Element::setPosition(Vector2<real> position)
{
	_x = position;
}

inline real Element::getRotation() const
{
	return _a;
}

inline void Element::setRotation(real radians)
{
	_a = radians;
}

inline int32 Element::getSapObjectId() const
{
	return _sapObjectId;
}

inline void Element::setSapObjectId(int32 sapObjectId)
{
	_sapObjectId = sapObjectId;
}

inline void* Element::getUserData() const
{
	return _userData;
}

inline void Element::setUserData(void* userData)
{
	_userData = userData;
}

inline Aabb2<real>& Element::getWorldBounds()
{
	return _worldBounds;
}

inline Matrix4<real> const& Element::getWorldMatrix() const
{
	return _worldMatrix;
}

inline Element::Element(real mass, Vector2<real> localPosition, real localAngle, p2Material const& material, uint32 plane, void* userData)
:	_owner(NULL),
 	_mass(0.f), _invMass(0.f), _inertia(0.f), _invInertia(0.f),_material(material),
 	_worldPosition(Vector2<real>::ZERO), _x(localPosition), _a(localAngle), _transform(), _worldMatrix(),
 	_volume(0.f), _centerOfMass(Vector2<real>::ZERO), _halfExtent(Vector2<real>::ZERO), _worldBounds(),
 	_sapObjectId(NULL_INDEX),
 	_plane(plane),
 	_userData(userData)
{
	setMass(mass);
}

}

#endif	//ELEMENT_H_
